package simpleftp.client.control;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;

import org.json.JSONException;
import org.json.simple.JSONObject;
import org.json.simple.JSONArray;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import simpleftp.client.gui.FTPClientWindow;
import simpleftp.server.Downloader;

public class CommandDispatcher implements Runnable {
	public static final String NL = "\n";
	private static final String USER = "USER";
	private static final String PASS = "PASS";
	private static final String SERVER = "SERVER";
	private static final String PORT = "PORT";
	private static final String SETTINGS_ROOT = "simpleftpsettings";
	private static final String DEFAULT_SETTINGS = SETTINGS_ROOT + "/default";
	private static final String LAST_SETTINGS = SETTINGS_ROOT + "/last";
	private static final String DEF_USER = "paris";
	private static final String DEF_PASS = "paris";
	private static final String DEF_SERVER = "127.0.0.1";
	private static final String DEF_PORT = "7802";
	private final ExecutorService executorPool;
	private final CompletionService<Boolean> completionPool;
	private final FTPClientWindow window;
	private final int numberOfCores;
	private final double blockingCoefficient = 0.9;
	private final int poolSize;
	private BlockingQueue<Command> commands;
	private SimpleFTP client;
	private Preferences userPrefs;
	private String server;
	private String port;
	private String user;
	private String pass;
	private boolean alreadyConnected;
	private String First_pwd;

	public CommandDispatcher(BlockingQueue<Command> cmd, FTPClientWindow wind) {
		this.commands = cmd;
		this.window = wind;
		alreadyConnected = false;
		numberOfCores = Runtime.getRuntime().availableProcessors();
		poolSize = (int) (numberOfCores / (1 - blockingCoefficient));
		executorPool = Executors.newFixedThreadPool(poolSize);
		completionPool = new ExecutorCompletionService<Boolean>(executorPool);
		client = new SimpleFTP();
	}

	@Override
	public void run() {
		try {
			loadStartupPreferences();
			Command cmd = this.commands.take();
			while (!cmd.equals(Command.QUIT)) {
				switch (cmd) {
				case CONNECT:
					connect();
					break;
				case DISCONNECT:
					disconnect();
					break;
				case STOR:
					upload();
					break;
				case LIST:
					listDir();
					break;
				case RETR:
					download();
					break;
				case PWD:
					pwd();
					break;
				case CLEAR:
					clearConsole();
					break;
				case LOADPREFS:
					loadPreferences();
					break;
				case SAVEPREFS:
					savePreferences();
					break;
				case DELPREF:
					deletePreference();
					break;
				case CWD:
						try {
							cwd();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
				default:
					break;
				}
				cmd = this.commands.take();
			}
			saveLastPreferences();
			disconnect();
			executorPool.shutdown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void clearConsole() {
		window.console.setText("");
	}

	private void saveLastPreferences() {
		userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
		userPrefs.put(USER, window.tfusername.getText());
		userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
		userPrefs.put(SERVER, window.tfservername.getText());
		userPrefs.put(PORT, window.tfport.getText());
		try {
			userPrefs.flush();
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}

	private void loadStartupPreferences() {
		try {
			userPrefs = Preferences.userRoot();
			if (!userPrefs.nodeExists(DEFAULT_SETTINGS)) {
				userPrefs = Preferences.userRoot().node(DEFAULT_SETTINGS);
				userPrefs.put(USER, DEF_USER);
				userPrefs.put(PASS, DEF_PASS);
				userPrefs.put(SERVER, DEF_SERVER);
				userPrefs.put(PORT, DEF_PORT);
				userPrefs.flush();
			}
			if (userPrefs.nodeExists(LAST_SETTINGS)) {
				userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
			} 
			window.tfusername.setText(userPrefs.get(USER, DEF_USER));
			window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
			window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
			window.tfport.setText(userPrefs.get(PORT, DEF_PORT));
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}
	
	private void loadPreferences() {
		try {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
			String[] userSavedPrefs = userPrefs.childrenNames();
			String s = (String) JOptionPane.showInputDialog(window,
					"Choose preference to load:", "Load Preferences",
					JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
			if ((s != null) && (s.length() > 0)) {
				userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
				window.tfusername.setText(userPrefs.get(USER, DEF_USER));
				window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
				window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
				window.tfport.setText(userPrefs.get(PORT, DEF_PORT));
				
				window.console.append(NL + "User preferences " + s + " loaded.");
			} else {
				window.console.append(NL + "Preferences loading operation cancelled.");
			}
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}

	private void savePreferences() {
		String s = (String) JOptionPane.showInputDialog(window,
				"Save the current connection info as:", "Save Preferences",
				JOptionPane.QUESTION_MESSAGE, null, null, null);
		if ((s != null) && (s.length() > 0)) {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
			userPrefs.put(USER, window.tfusername.getText());
			userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
			userPrefs.put(SERVER, window.tfservername.getText());
			userPrefs.put(PORT, window.tfport.getText());
			try {
				userPrefs.flush();
			} catch (BackingStoreException e) {
				window.console.append(NL + "Could not save preferences: "
						+ e.getMessage());
				e.printStackTrace();
			}
			window.console.append(NL + "Preferences saved as " + s);
		} else {
			window.console.append(NL + "Preferences saving operation cancelled.");
		}
	}
	
	private void deletePreference() {
		try {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
			String[] userSavedPrefs = userPrefs.childrenNames();
			String s = (String) JOptionPane.showInputDialog(window,
					"Choose preference to delete:", "Delete Preference",
					JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
			if ((s != null) && (s.length() > 0)) {
				userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
				userPrefs.removeNode();
				userPrefs.flush();
				window.console.append(NL + "User preferences " + s + " deleted.");
			} else {
				window.console.append(NL + "Preferences deletion operation cancelled.");
			}
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}
	
	private void download() {

		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			return;
		}

		//Recuperer le fichier selectionné
		TreePath path = window.tree.getSelectionPath();
		
		DefaultMutableTreeNode node = null;
		if(path !=null)
			node = (DefaultMutableTreeNode) path.getLastPathComponent();
		
		DefaultMutableTreeNode node1 = null;
		
		if (node != null){
			if(node.getChildCount() == 0)
			{
				String path1 = node.toString();
				node1 = (DefaultMutableTreeNode) node.getParent();
				while(node1.getParent().getParent() !=null)
				{
					path1 = node1.toString()+System.getProperty("file.separator")+path1;
					node1 = (DefaultMutableTreeNode) node1.getParent();
				}
				
				path1 = System.getProperty("user.dir") + System.getProperty("file.separator")+path1;
				window.console.append(NL +"Le noeud séléctionné est : "+path1);
				
				
				/** Get directory **/
				final JFileChooser fc = new JFileChooser();
				fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
				fc.setMultiSelectionEnabled(false);
				fc.setDragEnabled(true);
				int returnVal = fc.showDialog(window, "Download");
				if (returnVal == JFileChooser.APPROVE_OPTION) {
					File file = fc.getSelectedFile();
					
					/*** ICI ON A LE FILE **/
					//Download
					File f = new File(path1);
					
					Downloader d = new Downloader(f,file, window, server, port, user,pass);
				
					completionPool.submit(d);
					
				} else {
					window.console.append(NL + "Upload action cancelled.");
				}
				
				
				
				/**********************/
			}
			else 
				window.console.append(NL + "You need to select a file.");
				
		}else{
		 //Aucun noeud séléctonné!
			window.console.append(NL + "You need to select a file.");
		}
		
	}

	private void upload() {
		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			return;
		}
		final JFileChooser fc = new JFileChooser();
		fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
		fc.setMultiSelectionEnabled(true);
		fc.setDragEnabled(true);
		int returnVal = fc.showDialog(window, "Upload");
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			File[] files = fc.getSelectedFiles();
			List<Future<Boolean>> uploaders = new ArrayList<Future<Boolean>>(
					files.length);
			for (File f : files) {
				Uploader uploader = new Uploader(f, window, server, port, user,
						pass);
				uploaders.add(completionPool.submit(uploader));
			}
		} else {
			window.console.append(NL + "Upload action cancelled.");
		}
	}

	private void listDir() throws ParseException {
		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			return;
		}
		try {
			String dirContent = client.ls();
			window.console.append(NL + dirContent);

			window.root.removeAllChildren();
			DefaultMutableTreeNode userRoot;

			if(JsonTree.actif==true)
			{
				String json = client.getJson(); //pour le Json
				JSONParser parser = new JSONParser();			
				Object obj = parser.parse(json);			
				JSONObject jsonObject = (JSONObject) obj;
				
				userRoot = new DefaultMutableTreeNode(jsonObject.get("name").toString()); //pour le json
				try {
					populateTreeJson(userRoot,jsonObject);//pour le Json
					
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (JSONException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			else
			{

				File f = client.getFileTree();
				userRoot = new DefaultMutableTreeNode(f.getName());
				populateTree(userRoot, f);
			}
			

			window.root.add(userRoot);
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			e.printStackTrace();
		}

	}
	
	private void parcours_Json(DefaultMutableTreeNode userRoot, JSONObject j, int level)
	{
		String folderName = (String) j.get("name");

		JSONArray folder = (JSONArray) j.get("folder");
		JSONObject folder2;

		DefaultMutableTreeNode node = new DefaultMutableTreeNode(folderName);
		
		if(level != 1)
		{
		userRoot.add(node);
		}

		if(folder!=null)
		{
			for(int i=0;i<folder.size();i++)
			{
				folder2 = (JSONObject) folder.get(i);
				if(level != 1) parcours_Json(node,folder2, level+1);
				else parcours_Json(userRoot,folder2, level+1);
			}
		}
		

		
		
	}
	
	//JSON
	private void populateTreeJson(DefaultMutableTreeNode userRoot, JSONObject jsonObject) throws FileNotFoundException, IOException, ParseException, JSONException {

		parcours_Json(userRoot,jsonObject, 1);
		
		        
	}
	
	//Syncronised file
	private void populateTree(DefaultMutableTreeNode userRoot, File root) {
		// TO BE COMPLETED - WRITE THIS RECURSIVE METHOD - 7 LINES
			
		
		        for(File nom : root.listFiles()){  	
		        	if(nom.isDirectory())
		        	{
		        		DefaultMutableTreeNode node = new DefaultMutableTreeNode(nom.getName());
		        		populateTree(node,nom);
		        		userRoot.add(node);
		        	}
		        	else
		        	{
		        		userRoot.add(new DefaultMutableTreeNode(nom.getName()));
		        	}
		        }
		        if(root.listFiles().length == 0)
		        	userRoot.add(new DefaultMutableTreeNode());
	}

	private void pwd() {
		try {
			if (!alreadyConnected) {
				window.console.append(NL + "You are not connected to any server.");
				return;
			}
			String curDir = client.pwd();
			window.console.append(NL + "Current directory on FTP server: " + curDir);
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			e.printStackTrace();
		}
	}

	private void disconnect() {
		try {
			if (alreadyConnected) {
				client.disconnect();
				alreadyConnected = false;
				window.console.append(NL + "Now disconnected");
			} else {
				window.console.append(NL + "Already disconnected");
			}
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			e.printStackTrace();
		}

	}
	
	private void cwd() throws IOException{
		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			return;
		}		
		
		//Recuperer le fichier selectionné
		TreePath path = window.tree.getSelectionPath();
				
		DefaultMutableTreeNode node = null;
		if(path !=null)
			node = (DefaultMutableTreeNode) path.getLastPathComponent();
		DefaultMutableTreeNode node1 = null;
		
		if (node != null){
			if(node.getChildCount() > 0) //Si c'est un dossier
			{
				String path1 = node.toString();
				node1 = (DefaultMutableTreeNode) node.getParent();
				if(node1!= null){
					if( node1.getParent()!=null){
						//path1=node1.getParent().toString();
						while(node1.getParent().getParent() !=null)
						{
							path1 = node1.toString()+System.getProperty("file.separator")+path1;
							node1 = (DefaultMutableTreeNode) node1.getParent();
						}	
						
					}
					
					String[] result1 = First_pwd.split(System.getProperty("file.separator"));
					//last folder
					if(path1.equals(result1[result1.length-1])){ //si on a cliqué sur SimpleFTP-eleve
						path1=First_pwd;
					}
					else{
						path1=First_pwd+System.getProperty("file.separator")+path1;
					}
					window.console.append(NL +"Le dossier séléctionné est : "+path1);
					client.cwd(path1);
				}
				else{
					path1=node.toString();
					String[] result1 = First_pwd.split(System.getProperty("file.separator"));
					if(path1.equals(result1[result1.length-1])){ //si on a cliqué sur SimpleFTP-eleve
						System.out.println("if");
						path1=First_pwd;
					}
					else if(!path1.equals(result1[result1.length-1])){
						System.out.println("else");
						path1=First_pwd+System.getProperty("file.separator")+path1;
					}					
					window.console.append(NL +"Le dossier séléctionné est : "+path1);
					client.cwd(path1);	
				}
			}
			else{
				window.console.append(NL +"You should choose a directory");
			
			}
		}
			
	}
	

	private void connect() throws ParseException {
		if (alreadyConnected) {
			window.console.append(NL
					+ "You are already connected to this server.");
			return;
		}
		server = window.tfservername.getText();
		if (server == null || "".equals(server)) {
			window.console.append(NL
					+ "You must specify the server IP address.");
			return;
		}
		port = window.tfport.getText();
		if (port == null || "".equals(port)) {
			window.console.append(NL
					+ "You must specify the server port I must connect to.");
			return;
		}
		user = window.tfusername.getText();
		if (user == null || "".equals(user))
			user = "anonymous";
		pass = new String(window.pfuserpass.getPassword());
		if (pass == null || "".equals(pass))
			pass = "anonymous";
		try {
			client.connect(server, Integer.parseInt(port), user, pass);
			alreadyConnected = true;
			window.console.append(NL + "Now connected");
			pwd();
			listDir();
			First_pwd =System.getProperty("user.dir").toString();
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			e.printStackTrace();
		} catch (NumberFormatException nfe) {
			window.console.append(NL + nfe.getMessage());
			nfe.printStackTrace();
		}
	}
}
